package AmbientNetwork;

import java.util.*;

/**
 * Az ambiens hálózatok egymással történő cselekvéseit megvalósító osztály.
 * @author D-Torus-Team
 */
public class ConnectionLogic {

    /**
     * ID generáláshoz.
     */

    private static long id;

    ConnectionLogic() {
    }

    /**
     * Inicializálás
     */

    public static void init(){
        id=1000;
    }

    /**
     * Megnézi a két paraméter policy értékeit és lefuttatja a helyzetnek megfelelő függvényt.
     * Amennyiben az egyik Node szolgáltatott értéke nagyobb vagy egyenlő a másik elvárt értékénél, és fordítva, akkor abszorbció lép fel.
     * Ha ez nem teljesül akkor a gateway függvény fut le.
     * @param a Egyik Node
     * @param b Másik Node
     * @return Visszaadja a újonnan generélt AmbientNetworks objektumot
     */

    public static Node merge(Node a, Node b) {
        if ((a.getPolicy().getRequired() <= b.getPolicy().getProvided()) && (b.getPolicy().getRequired() <= a.getPolicy().getProvided())) {
            System.out.print("merge lefutott -> abs lesz (" + a.getID() +"("); a.getPolicy().printPolicy(); System.out.print( ") " + b.getID()+"("); b.getPolicy().printPolicy(); System.out.println(") )");
            return absorbtion(a, b);
        } else {
            System.out.print("merge lefutott -> Gateway lesz (" + a.getID() +"("); a.getPolicy().printPolicy(); System.out.print( ") " + b.getID()+"("); b.getPolicy().printPolicy(); System.out.println(") )");
            return gateway(a, b);
        }
    }

    /**
     * Kiszámolja az új policy értékét abszorbció esetén.
     * @param a Node
     * @param b Node
     * @return Policy
     */

    private static Policy newPolicyAbs(Node a, Node b) {
        int newPolicyRequired;
        int newPolicyProvided = (int) ((a.getPolicy().getProvided() + b.getPolicy().getProvided()) / 2); // az új szolgáltatott érték a két paraméterként megadott Node szolgáltatott értékeinek az átlaga
        if (a.getPolicy().getRequired() >= b.getPolicy().getRequired()) { // amennyiben az "a" Node elvárt értéke nagyobb akkor azt állítjuk be elvártnak
            newPolicyRequired = a.getPolicy().getRequired();
        } else {
            newPolicyRequired = b.getPolicy().getRequired(); // amennyiben a "b" Node elvárt értéke nagyobb akkor azt állítjuk be elvártnak
        }
        Policy newPolicy = new Policy(newPolicyProvided, newPolicyRequired);
        return newPolicy;
    }

    /**
     * Kiszámolja az új policy értékét gateway esetén.
     * @param a Node
     * @param b Node
     * @return Policy
     */

    private static Policy newPolicyGate(Node a, Node b) {
        int newPolicyRequired;
        int newPolicyProvided = (int) ((a.getPolicy().getProvided() + b.getPolicy().getProvided()) / 2); // az új szolgáltatott érték a két paraméterként megadott Node szolgáltatott értékeinek az átlaga
        if (a.getPolicy().getRequired() >= b.getPolicy().getRequired()) {// amennyiben a "b" Node elvárt értéke kisebb akkor azt állítjuk be elvártnak
            newPolicyRequired = b.getPolicy().getRequired();
        } else {
            newPolicyRequired = a.getPolicy().getRequired(); // amennyiben a "a" Node elvárt értéke kisebb akkor azt állítjuk be elvártnak
        }
        Policy newPolicy = new Policy(newPolicyRequired, newPolicyProvided);
        return newPolicy;
    }

    /**
     * Létrehoz egy új listát a két paraméterül megadott Node szomszédlistájából.
     * @param a
     * @param b
     * @return
     */

    private static List<Node> newNeighbours(Node a, Node b) {
        List<Node> newneighbours = new ArrayList<Node>(); // létrehozunk egy üres listát
        Iterator<Node> ita = a.getNeighbours().iterator(); // iterátort hozunk létre az egyik Node szomszédlistájára
        while (ita.hasNext()) { // végigmegyünk a Node szomszédlistáján
            Node temp = ita.next();
            if (temp != b) { // amennyiben nem egyezik meg a másik Node-al hozzáadjuk az új listához
                newneighbours.add(temp);
            }
        }
        Iterator<Node> itb = b.getNeighbours().iterator(); // iterátort hozunk létre a másik Node szomszédlistájára
        while (itb.hasNext()) { // végigmegyünk a Node szomszédlistáján
            Node temp = itb.next();
            if (temp != a) { // amennyiben nem egyezik meg a másik Node-al hozzáadjuk az új listához
                newneighbours.add(temp);
            }
        }
        return newneighbours;
    }

    /**
     * Frissítjük a szomszédok szomszédlistáit.
     * @param n Neighbours
     * @param a Node
     * @param b Node
     * @param c AmbientNetworks
     */

    private static void refreshNeighbours(List<Node> n, Node a, Node b, Node c) {
        ListIterator<Node> it = n.listIterator(); // a paraméterül megadott szomszédlistára létrehozunk egy iterátort
        while (it.hasNext()) {
            Node temp = it.next(); // végigmegyünk a listán
            temp.getNeighbours().remove(a); // kivesszük a paraméterül megadott Node-ot
            temp.getNeighbours().add(c); // hozzáadjuk a paraméterül megadott új AmbientNetworks-öt
            temp.getNeighbours().remove(b); // kivesszük a paraméterül megadott Node-ot

        }
    }

    /**
     * Létrehoz egy új listát a paraméterül megadott két Node tagjaiból.
     * @param a Node
     * @param b Node
     * @return List<Node>
     */

    private static List<Node> newMembers(Node a, Node b) {
        List<Node> newMembers = new ArrayList(); // új lista létrehozása

        if (a.getIsPeer()) {
            newMembers.add(a); // ha a paraméterül megadott Node csak egy Peer akkor hozzáadjuk az új listához
        } else {
            newMembers.addAll(a.getMembers()); // ha a paraméterül megadott Node egy AmbientNetworks akkor hozzáadjuk a taglistájának az összes tagját az új listához
        }

        if (b.getIsPeer()) {
            newMembers.add(b); // ha a paraméterül megadott Node csak egy Peer akkor hozzáadjuk az új listához
        } else {
            newMembers.addAll(b.getMembers()); // ha a paraméterül megadott Node egy AmbientNetworks akkor hozzáadjuk a taglistájának az összes tagját az új listához
        }

        return newMembers;
    }

    /**
     * Beállítjuk az AmbientNetworks objektum tagjainak az AnID változóját, amely megadja, hogy melyik AmbientNetworks-ben található meg.
     * @param a AmbientNetworks
     */

    private static void setAnID(AmbientNetworks a) {
        Iterator<Node> it = a.getMembers().iterator();
        while (it.hasNext()) {
            it.next().addAnID(a.getID()); // Iterátorral végigmegyünk az AmbientNetworks objektum tagjain is átállítjuk az AnID-t
        }
    }

    /**
     * Beállítjuk az AmbientNetworks objektum hierarchia szintjét. A legalsó szint a nulla, majd növekszik az értéke.
     * @param a Node
     * @param b Node
     * @param an AmbientNetworks
     */

    private static void setNewDepth(Node a, Node b, AmbientNetworks an) {
        if (a.getDepth() >= b.getDepth()) { //megnézzük hogy a két Node közül melyiknek nagyobb a mélysége, és azt állítjuk be egyyel növelve.
            an.setDepth(a.getDepth() + 1);
        } else {
            an.setDepth(b.getDepth() + 1);
        }
    }

   /**
    * Ez a függvény valósítja meg az absorbciót két Node között.
    * @param a Node
    * @param b Node
    * @return AmbientNetworks
    */
    private static AmbientNetworks absorbtion(Node a, Node b) {

        long newid = id++; // új ID létrehozása
        AmbientNetworks an = new AmbientNetworks(newid, newPolicyAbs(a, b), newNeighbours(a, b)); // új AmbientNetworks létrehozása
        an.setMembers(newMembers(a, b)); // tagok beállítása
        refreshNeighbours(newNeighbours(a, b), a, b, an); // szomszédlisták frissítése
        setAnID(an); // AnID-k beállítása
        System.out.println("absorbtion lefutott (" +a.getID()+"+"+b.getID()+"="+an.getID()+")" );
        return an;
    }

    /**
     * Ez a függvény valósítja meg a gatewayinget két Node között
     * @param a Node
     * @param b Node
     * @return AmbientNetworks
     */

    private static AmbientNetworks gateway(Node a, Node b) {
        AmbientNetworks antmp = new AmbientNetworks(); // új AmbientNetwroks létrehozása
//        System.out.println("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD");
//        a.printMessage();
//        b.printMessage();
//        System.out.println("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD");

        if (a.getIsPeer() && b.getIsPeer()) { // amennyiben mindkét Node Peer a gateway nem jöhet létre
            
            //   throw new Exception("Gatewaying denied!");
        } else if (a.getIsPeer()) { // amnnyiben az egyik Node Peer
            Iterator<Node> it = b.getMembers().iterator();
            while (it.hasNext()) {
                Node temp = it.next(); // végigmegyünk a nem Peer Node tagjain
                if (a.getPolicy().getProvided() >= temp.getPolicy().getRequired() && temp.getPolicy().getProvided() >= a.getPolicy().getRequired() ) { // ha találunk olyan tagot akivel lehet abszorbció
                    AmbientNetworks an = absorbtion(a, temp); // abszorbciót lefuttatunk a Peer és a kiválaszott tag között
                    setNewDepth(a, temp, an);
                    temp.setIsSuperPeer(true); // beállítjuk a kiválaszottat a Node-jának superpeerjévé
                    //a.setIsSuperPeer(true);

                    antmp = an;
                    break;
                }
            }
        } else if (b.getIsPeer()) { // amennyiben a másik Node peer
            Iterator<Node> it = a.getMembers().iterator();
            while (it.hasNext()) {
                Node temp = it.next(); // végigmegyünk a nem Peer Node tagjain
                if (b.getPolicy().getProvided() >= temp.getPolicy().getRequired() && temp.getPolicy().getProvided() >= b.getPolicy().getRequired()) { // ha találunk olyan tagot akivel lehet abszorbció
                    AmbientNetworks an = absorbtion(b, temp); // abszorbciót lefuttatunk a Peer és a kiválaszott tag között
                    setNewDepth(temp, b, an);
                    temp.setIsSuperPeer(true); // beállítjuk a kiválaszottat a Node-jának superpeerjévé
                    //b.setIsSuperPeer(true);
                    antmp = an;
                    break;
                    
                }
               
            }
        } else { // amennyiben egyik Node sem Peer
            ListIterator<Node> ita = a.getMembers().listIterator();
            ListIterator<Node> itb = b.getMembers().listIterator();
            while (ita.hasNext()) {
                Node tempa = ita.next(); // vesszük egyesével az egyik Node tagjait
                while (itb.hasNext()) {
                    Node tempb = itb.next(); // mellé vesszük egyesével a másik Node tagjait
                    if (tempa.getPolicy().getProvided() >= tempb.getPolicy().getRequired() && tempb.getPolicy().getProvided() >= tempa.getPolicy().getRequired()) { // ha találunk megfelelő policy értékeket
                        AmbientNetworks an = absorbtion(tempa, tempb); // lefutatjuk a kettő között az abszorbciót
                        setNewDepth(tempa, tempb, an);
                        tempa.setIsSuperPeer(true); // beállítjuk mindkettőt a Node-ja superpeerjévé
                        tempb.setIsSuperPeer(true);
                        antmp = an;
                        break;
                    } 
                    while (itb.hasPrevious()) {
                        itb.previous(); //visszalépteti az iterátort legelőre
                    }
                    
                    //itb=b.getMembers().listIterator();
                }
            }
        }

        if(antmp.getID()==0){System.out.println("Gateway denied"); } else { System.out.println("Gateway lefutott "); }
        return antmp;
    }

    public static boolean leave(Node a) {
        throw new UnsupportedOperationException();
        //megírjuk nyáron, ha lesz pínz...
    }
}
